#ifndef CHECKABLE_MENU_H
#define CHECKABLE_MENU_H

#include <QMenu>
#include <QDebug>
#include <map>
#include <tuple>
#include <vector>
#include <utility>

namespace checkable_menu
{

static const QString key_root_node = "key_root_node";

bool is_all_checked      (const QList<QAction *> actions);
void set_parent_checked  (QMenu *menu, bool check);
void set_children_checked(QMenu *menu, bool check);

class tree
{
public:
    struct node {
        typedef std::tuple<QString, QString, int, QVariant>  data_t;       // key, text, type, value
        typedef std::vector<data_t>                          data_list_t;

        node()
            : key("")
            , text("")
            , type(-1)
            , value(-1U) {
        }

        node(const data_t &data)
            : key  (std::get<0>(data))
            , text (std::get<1>(data))
            , type (std::get<2>(data))
            , value(std::get<3>(data)) {
        }

        bool empty() const {
            return key.isEmpty();
        }

        QString               key;
        QString               text;
        int                   type;
        QVariant              value;
        std::vector<QString>  children;
    };

public:
    tree()
        : root_node_(std::make_tuple(key_root_node, "", -1, 0))
    {
        map_.insert(std::make_pair(key_root_node, root_node_));
    }

    void add_node(const node::data_list_t &list)
    {
        if (list.empty()) {
            qWarning() << "tree::add_node > list with data can't be empty";
            return;
        }

        for (auto it : list) {
            if (std::get<0>(it) == key_root_node) {
                qWarning() << "tree::add_node > key" << key_root_node << "can't be used, because it reserved for root node, use another key";
                return;
            }
        }

        if (!root_node_.children.empty()) {
            root_node_.children.clear();
            map_.clear();
            map_.insert(std::make_pair(key_root_node, root_node_));
        }

        for(auto it : list)
            root_node_.children.push_back(std::get<0>(it));

        for (auto it : list)
            map_.insert(std::make_pair(std::get<0>(it), node(it)));
    }

    void add_node(const QString &parent_key, const node::data_list_t &list)
    {
        if(parent_key == key_root_node) {
            qWarning() << "tree::add_node > key" << key_root_node << "can't be used, because it reserved for root node, use another key";
            return;
        }

        if (parent_key.isEmpty()) {
            qWarning() << "tree::add_node > parent can't be empty";
            return;
        }

        if (list.empty()) {
            qWarning() << "tree::add_node > list with data can't be empty";
            return;
        }

        for (auto it : list) {
            if (std::get<0>(it) == key_root_node) {
                qWarning() << "tree::add_node > key" << key_root_node << "can't be used, because it reserved for root node, use another key";
                return;
            }
            auto iter = map_.find(std::get<0>(it));
            if (iter != map_.end()) {
                qWarning() << "tree::add_node >" << std::get<0>(it) << "key already exists";
                return;
            }
        }

        auto parent_it = map_.find(parent_key);
        if (parent_it == map_.end()) {
            qWarning() << "tree::add_node > no such parent key" << parent_key << "exists";
            return;
        }

        parent_it->second.children.clear();

        for(auto it : list)
            parent_it->second.children.push_back(std::get<0>(it));

        for (auto it : list)
            map_.insert(std::make_pair(std::get<0>(it), node(it)));
    }

    std::vector<QString> get_root_node_children() const
    {
        return root_node_.children;
    }

    tree::node get_node(const QString &key) const
    {
        auto it = map_.find(key);
        if (it == map_.end())
            return node();
        else
            return it->second;
    }

    void print()
    {
        print_level(root_node_.children, "");
    }

private:
    void print_level(const std::vector<QString> nodes, QString tab)
    {
        tab += "\t";
        for (auto it : nodes) {
            auto iter = map_.find(it);
            if (iter != map_.end()) {
                qWarning() << tab << iter->second.key << iter->second.value.toUInt();
                print_level(iter->second.children, tab);
            }
        }
    }

private:
    std::map<QString, node>  map_;
    node                     root_node_;
};

class menu
    : public QMenu
{
    Q_OBJECT

public:
    explicit menu(QWidget *parent = 0);
    explicit menu(const QString &title, QWidget *parent = 0);
    ~menu();
    void set_checked(bool checked);

private:
    void mouseReleaseEvent(QMouseEvent *event);
};

} // end of namespace checkable_menu

#endif // CHECKABLE_MENU_H
